/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

/**
 * System Includes:
 **/
#include "commonheaders.h"
#include "m_contacts.h"

/**
 * name:	DBGetSubContactSettingStrEx
 * desc:	This function tries to read a setting from a certain module (e.g. USERINFO) and if failed it 
 *			tries once again with the baseprotocol of the subcontact of a meta contact (e.g. ICQ)
 * params:	hContact	 - handle of the meta contact whose subcontact's setting is to read
 *			iSubContact	 - number to the subcontact to read the settings from
 *			pszModule	 - desired module
 *			pszBaseProto - the protocol to fall back to
 *			pszSetting	 - the desired setting to read
 *			pdbv		 - pointer to a DBVARIANT structure retrieving the result, the type member must be set to the desired string format!
 * return:	0 if setting was found and read correctly, 1 if any error occured or setting was not found
 **/
static INT DBGetSubContactSettingStrEx(HANDLE hContact, INT iSubContact, LPCSTR pszModule, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	if(iSubContact != -1) {
		HANDLE hSubContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hContact, iSubContact);

		if(hSubContact != NULL && (INT)hSubContact != CALLSERVICE_NOTFOUND) {
			LPCSTR pszBaseProto = DBGetContactBaseProto(hSubContact);

			if((INT)pszBaseProto == CALLSERVICE_NOTFOUND)
				pszBaseProto = NULL;
			return DBGetSettingStrEx(hSubContact, pszModule, pszBaseProto, pszSetting, pdbv);
		}
	}
	return 1;
}

/**
 * name:	DBGetContactInfoSetting
 * desc:	This function tries to read a setting from a certain module (e.g. USERINFO) and if failed it 
 *			tries once again with the baseprotocol of the contact (e.g. ICQ). If nothing was found anyway
 *			and this is an metacontact it can have a look into subcontacts to retrieve the information.
 *			This depends on the settings the user did.
 * params:	ci			 - pointer to a CONTACTINFO structure holding information about the contact
 *			pszSetting	 - the desired setting to read
 *			pdbv		 - pointer to a DBVARIANT structure retrieving the result, the type member must be set to the desired string format!
 * return:	0 if setting was found and read correctly, 1 if any error occured or setting was not found
 **/
static INT DBGetContactInfoSetting(CONTACTINFO *ci, LPCSTR pszSetting, DBVARIANT *pdbv)
{
	BYTE type;

	if(ci == NULL || pszSetting == NULL || *pszSetting == NULL || pdbv == NULL)
		return 1;

	type = pdbv->type = (ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ;

	if(!DBGetSettingStrEx(ci->hContact, USERINFO, ci->szProto, pszSetting, pdbv))
		return 0;
	
	if(IsMetaContactProtoWantToScan(ci->szProto)) {
		const INT iDefault = CallService(MS_MC_GETDEFAULTCONTACTNUM, (WPARAM)ci->hContact, NULL);
		
		if(iDefault > -1 && iDefault < INT_MAX) {
			INT i, numSubs;

			pdbv->type = type;
			if(!DBGetSubContactSettingStrEx(ci->hContact, iDefault, USERINFO, pszSetting, pdbv))
				return 0;

			numSubs = CallService(MS_MC_GETNUMCONTACTS, (WPARAM)ci->hContact, NULL);
			if(numSubs < INT_MAX) {
				for(i = 0; i < numSubs; i++) {
					pdbv->type = type;
					if(i != iDefault && DBGetSubContactSettingStrEx(ci->hContact, i, USERINFO, pszSetting, pdbv))
						return 0;
				}
			}
		}
	}
	pdbv->type = DBVT_DELETED;
	return 1;
}

/**
 * name:	DBVariantToCI
 * desc:	write value from DBVARIANT to CONTACTINFO correctly
 * params:	ci		- destination CONTACTINFO structure
 *			pdbv	- source DBVARIANT structure
 * return:	0 on success, 1 on failure
 **/
static INT DBVariantToCI(CONTACTINFO *ci, DBVARIANT *pdbv)
{
	if(ci && pdbv) {
		switch(pdbv->type) {
			case DBVT_ASCIIZ:
			case DBVT_WCHAR:
				ci->pszVal = pdbv->ptszVal;
				ci->type = (ci->pszVal != NULL) ? CNFT_ASCIIZ : 0;
				return ci->type == 0;
			case DBVT_BYTE:
				ci->type = CNFT_BYTE;
				ci->bVal = pdbv->bVal;
				return 0;
			case DBVT_WORD:
				ci->type = CNFT_WORD;
				ci->wVal = pdbv->wVal;
				return 0;
			case DBVT_DWORD:
				ci->type = CNFT_DWORD;
				ci->dVal = pdbv->dVal;
				return 0;
		}
		DBFreeVariant(pdbv);
	}
	ci->type = 0;
	return 1;
}

/**
 * name:	DBVariantToCI
 * desc:	write value from DBVARIANT to CONTACTINFO correctly
 * params:	ci		- destination CONTACTINFO structure
 *			pdbv	- source DBVARIANT structure
 * return:	0 on success, 1 on failure
 **/
static INT DBVariantToCIAsStr(CONTACTINFO *ci, DBVARIANT *pdbv)
{
	if(ci && pdbv) {
		ci->pszVal = (ci->dwFlag & CNF_UNICODE) ? (LPTSTR)DBVariantToStringW(pdbv) : (LPTSTR)DBVariantToStringA(pdbv);
		ci->type = (ci->pszVal != NULL) ? CNFT_ASCIIZ : 0;
		DBFreeVariant(pdbv);
		return ci->type == 0;
	}
	ci->type = 0;
	return 1;
}

/**
 * name:	DBGetContactInfoSetting
 * desc:	This function tries to read a setting from a certain module (e.g. USERINFO) and if failed it 
 *			tries once again with the baseprotocol of the contact (e.g. ICQ). If nothing was found anyway
 *			and this is an metacontact it can have a look into subcontacts to retrieve the information.
 *			This depends on the settings the user did.
 * params:	ci			 - pointer to a CONTACTINFO structure holding information about the contact
 *			pszSetting	 - the desired setting to read
 * return:	0 if setting was found and read correctly, 1 if any error occured or setting was not found
 **/
static __forceinline INT DBGetContactInfoSetting(CONTACTINFO *ci, LPCSTR pszSetting)
{
	DBVARIANT dbv;
	return (DBGetContactInfoSetting(ci, pszSetting, &dbv) || DBVariantToCI(ci, &dbv));
}

/**
 * name:	DBGetContactInfoSettingString
 * desc:	This function calls DBGetContactInfoSetting to get a contact information and forces the return value to be a string in any case.
 * params:	ci			 - pointer to a CONTACTINFO structure holding information about the contact
 *			pszSetting	 - the desired setting to read
 * return:	0 if setting was found and read correctly, 1 if any error occured or setting was not found
 **/
static __forceinline INT DBGetContactInfoSettingString(CONTACTINFO *ci, LPCSTR pszSetting)
{
	DBVARIANT dbv;
	return (DBGetContactInfoSetting(ci, pszSetting, &dbv) || DBVariantToCIAsStr(ci, &dbv));
}

/**
 * name:	GCIFirstLast
 * desc:	Format the full name for the contact.
 * params:	ci	- CONTACTINFO structure
 * return:	0 if name was successfully created, 1 otherwise
 **/
static INT GCIFirstLast(CONTACTINFO *ci)
{
	INT ret = 1;
	DWORD cchName;
	DBVARIANT dbv, dbv2;

	ZeroMemory(&dbv, sizeof(dbv));
	ZeroMemory(&dbv2, sizeof(dbv2));

	DBGetContactInfoSetting(ci, SET_CONTACT_FIRSTNAME, &dbv);
	DBGetContactInfoSetting(ci, SET_CONTACT_LASTNAME, &dbv2);
	if(dbv.type == DBVT_DELETED && dbv2.type == DBVT_DELETED)  return 1;

	cchName = 2;
	if(ci->dwFlag & CNF_UNICODE) {
		LPWSTR buf;
		if(dbv.pwszVal) cchName += wcslen(dbv.pwszVal);
		if(dbv2.pwszVal) cchName += wcslen(dbv2.pwszVal);
		
		if(cchName > 2 && (buf = (LPWSTR)mir_alloc(sizeof(WCHAR) * cchName))) {
			ci->pszVal = (LPTSTR)buf;
			ci->type = CNFT_ASCIIZ;
			if(dbv.pwszVal) {
				wcscpy(buf, dbv.pwszVal);
				buf += wcslen(dbv.pwszVal);
			}
			if(dbv2.pwszVal) {
				if(dbv.pwszVal) *(buf++) = ' ';
				wcscpy(buf, dbv2.pwszVal);
			}
			ci->pszVal[cchName-1] = 0;
			ret = 0;
		}
	}
	else {
		LPSTR buf;

		if(dbv.pszVal) cchName += strlen(dbv.pszVal);
		if(dbv2.pszVal) cchName += strlen(dbv2.pszVal);

		if(cchName > 2 && (buf = (LPSTR)mir_alloc(sizeof(CHAR) * cchName))) {
			ci->pszVal = (LPTSTR)buf;
			ci->type = CNFT_ASCIIZ;
			if(dbv.pszVal) {
				strcpy(buf, dbv.pszVal);
				buf += strlen(dbv.pszVal);
			}
			if(dbv2.pszVal) {
				if(dbv.pszVal) *(buf++) = ' ';
				strcpy(buf, dbv2.pszVal);
			}
			ci->pszVal[cchName-1] = 0;
			ret = 0;
		}
	}
	DBFreeVariant(&dbv);
	DBFreeVariant(&dbv2);
	return ret;
}

/**
 * name:	GCICountry
 * desc:	return the country name
 * params:	ci			 - pointer to a CONTACTINFO structure holding information about the contact
 *			pszSetting	 - the desired setting to read the countrys id from
 * return:	0 if setting was found and read correctly, 1 if any error occured or setting was not found
 **/
static INT GCICountry(CONTACTINFO *ci, LPCSTR pszSetting)
{
	DBVARIANT dbv;

	if(!DBGetContactInfoSetting(ci, pszSetting, &dbv)) {
		// country is stored as ascii
		if(dbv.type == ((ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ)) {
			ci->pszVal = dbv.ptszVal;
			ci->type = CNFT_ASCIIZ;
			return 0;
		}
		// country id was safed in database
		else {
			INT i, countryCount;
			struct CountryListEntry *countries;

			CallService(MS_UTILS_GETCOUNTRYLIST, (WPARAM)&countryCount, (LPARAM)&countries);
			for(i = 0; i < countryCount; i++) {
				if(countries[i].id != dbv.wVal) continue;
				ci->type = CNFT_ASCIIZ;
				ci->pszVal = (ci->dwFlag & CNF_UNICODE) ? (LPTSTR)MultiByteToWideChar(countries[i].szName) : (LPTSTR)mir_strdup(countries[i].szName);
				DBFreeVariant(&dbv);
				return ci->pszVal == NULL;
			}
			DBFreeVariant(&dbv);
		}
	}
	return 1;
}

/**
 * name:	GetContactInfo
 * desc:	main service routine
 * params:	wParam	 - not used
 *			lParam	 - pointer to a CONTACTINFO structure which tells what information is desired
 * return:	0 if contact information was found and read correctly, 1 if any error occured or setting was not found
 **/
INT GetContactInfo(WPARAM wParam, LPARAM lParam) 
{
	CONTACTINFO *ci = (CONTACTINFO*)lParam;
	DBVARIANT dbv;

	if(ci == NULL || ci->cbSize != sizeof(CONTACTINFO)) return 1;
	if(ci->szProto == NULL) {
		if(ci->hContact == NULL) return 1;
		ci->szProto = (LPSTR)DBGetContactBaseProto(ci->hContact);
		if(ci->szProto == NULL || (INT)ci->szProto == CALLSERVICE_NOTFOUND) return 1;
	}

	switch(ci->dwFlag & 0x7F) {

		//
		// contact name
		//
		case CNF_FIRSTNAME:		return DBGetContactInfoSettingString(ci, SET_CONTACT_FIRSTNAME);
		case CNF_LASTNAME:		return DBGetContactInfoSettingString(ci, SET_CONTACT_LASTNAME);
		case CNF_NICK:			return DBGetContactInfoSettingString(ci, SET_CONTACT_NICK);
		case CNF_FIRSTLAST:		return GCIFirstLast(ci);
		case CNF_CUSTOMNICK:
			dbv.type = (ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ;
			if(ci->hContact != NULL && !DBGetSettingStr(ci->hContact, "CList", "MyHandle", &dbv) && !DBVariantToCIAsStr(ci, &dbv))
				return 0;
			DBFreeVariant(&dbv);
			break;
		
		//
		// private contact
		//
		case CNF_STREET:		return DBGetContactInfoSettingString(ci, SET_CONTACT_STREET);
		case CNF_ZIP:			return DBGetContactInfoSettingString(ci, SET_CONTACT_ZIP);
		case CNF_CITY:			return DBGetContactInfoSettingString(ci, SET_CONTACT_CITY);
		case CNF_STATE:			return DBGetContactInfoSettingString(ci, SET_CONTACT_STATE);
		case CNF_COUNTRY:		return GCICountry(ci, SET_CONTACT_COUNTRY);
		case CNF_PHONE:			return DBGetContactInfoSettingString(ci, SET_CONTACT_PHONE);
		case CNF_FAX:			return DBGetContactInfoSettingString(ci, SET_CONTACT_FAX);
		case CNF_CELLULAR:		return DBGetContactInfoSettingString(ci, SET_CONTACT_CELLULAR);
		case CNF_EMAIL:			return DBGetContactInfoSettingString(ci, SET_CONTACT_EMAIL);
		case CNF_HOMEPAGE:		return DBGetContactInfoSettingString(ci, SET_CONTACT_HOMEPAGE);

		//
		// company information
		//
		case CNF_CONAME:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY);
		case CNF_CODEPT:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_DEPARTMENT);
		case CNF_COPOSITION:	return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_POSITION);
		case CNF_COSTREET:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_STREET);
		case CNF_COZIP:			return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_ZIP);
		case CNF_COCITY:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_CITY);
		case CNF_COSTATE:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_STATE);
		case CNF_COCOUNTRY:		return GCICountry(ci, SET_CONTACT_COMPANY_COUNTRY);
		case CNF_COPHONE:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_PHONE);
		case CNF_COFAX:			return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_FAX);
		case CNF_COCELLULAR:	return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_CELLULAR);
		case CNF_COEMAIL:		return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_EMAIL);
		case CNF_COHOMEPAGE:	return DBGetContactInfoSettingString(ci, SET_CONTACT_COMPANY_HOMEPAGE);

		//
		// personal information
		//
		case CNF_ABOUT:			return DBGetContactInfoSettingString(ci, SET_CONTACT_ABOUT);
		case CNF_MYNOTES:		return DBGetContactInfoSettingString(ci, SET_CONTACT_MYNOTES);
		case CNF_AGE:			return DBGetContactInfoSetting(ci, SET_CONTACT_AGE);
		case CNF_GENDER:
			if(!DBGetContactInfoSetting(ci, SET_CONTACT_GENDER, &dbv)) {
				// gender must be byte and either M or F
				if(dbv.type == DBVT_BYTE) {
					ci->type = CNFT_BYTE;
					ci->bVal = (dbv.bVal == 'M' || dbv.bVal == 'F') ? dbv.bVal : 0;
					return 0;
				}
			}
			break;
		case CNF_BIRTHDAY:
		{
			MAnnivDate mda;
			if(!mda.DBGetBirthDate(ci->hContact, ci->szProto)) {
				ci->bVal = (BYTE)mda.Day();
				ci->type = CNFT_BYTE;
				return 0;
			}
			break;
		}
		case CNF_BIRTHMONTH:
		{
			MAnnivDate mda;
			if(!mda.DBGetBirthDate(ci->hContact, ci->szProto)) {
				ci->bVal = (BYTE)mda.Month();
				ci->type = CNFT_BYTE;
				return 0;
			}
			break;
		}
		case CNF_BIRTHYEAR:	
		{
			MAnnivDate mda;
			if(!mda.DBGetBirthDate(ci->hContact, ci->szProto)) {
				ci->wVal = mda.Year();
				ci->type = CNFT_WORD;
				return 0;
			}
			break;
		}
		case CNF_BIRTHDATE:
		{
			MAnnivDate mda;
			SYSTEMTIME st;
			
			if(!mda.DBGetBirthDate(ci->hContact, ci->szProto)) {
				st = mda.SystemTime();

				ci->pszVal = NULL;
				if(ci->dwFlag & CNF_UNICODE) {
					WCHAR wszDate[80];
					
					if(GetDateFormatW(LOCALE_USER_DEFAULT, wParam == 1 ? DATE_LONGDATE : DATE_SHORTDATE, &st, NULL, wszDate, SIZEOF(wszDate))) {
						ci->pszVal = (LPTSTR)mir_wcsdup(wszDate);
					}
				}
				else {
					CHAR szDate[80];
					
					if(GetDateFormatA(LOCALE_USER_DEFAULT, wParam == 1 ? DATE_LONGDATE : DATE_SHORTDATE, &st, NULL, szDate, SIZEOF(szDate))) {
						ci->pszVal = (LPTSTR)mir_strdup(szDate);
					}
				}
				ci->type = (ci->pszVal != NULL) ? CNFT_ASCIIZ : 0;
				return ci->type == 0;
			}
			break;
		}
		case CNF_TIMEZONE: 
			if(!DBGetContactInfoSetting(ci, SET_CONTACT_TIMEZONE, &dbv)) {
				ci->pszVal = NULL;
				if(ci->dwFlag & CNF_UNICODE) {
					WCHAR str[80];
					if(mir_snprintfW(str, SIZEOF(str), dbv.cVal ? L"GMT%+d:%02d" : L"GMT", -dbv.cVal / 2, (dbv.cVal & 1) * 30) > 0)
						ci->pszVal = (LPTSTR)mir_wcsdup(str);
				}
				else {
					CHAR str[80];
					if(mir_snprintfA(str, SIZEOF(str), dbv.cVal ? "GMT%+d:%02d" : "GMT", -dbv.cVal / 2, (dbv.cVal & 1) * 30) > 0)
						ci->pszVal = (LPTSTR)mir_strdup(str);
				}
				ci->type = (ci->pszVal != NULL) ? CNFT_ASCIIZ : 0;
				return ci->type == 0;
			}
			break;

		//
		// information about IM specific stuff
		//
		case CNF_UNIQUEID:
		{
			// protocol must define a PFLAG_UNIQUEIDSETTING
			LPCSTR uidSetting = (LPCSTR)CallProtoService(ci->szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
			
			if((INT)uidSetting != CALLSERVICE_NOTFOUND && uidSetting != NULL) {	
				dbv.type = (ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ;
				if(!DBGetSettingStr(ci->hContact, ci->szProto, uidSetting, &dbv) && !DBVariantToCI(ci, &dbv)) {
					return 0;
				}
				DBFreeVariant(&dbv);
			}
			break;
		}
		case CNF_DISPLAYNC:
		case CNF_DISPLAY:
		{
			INT i;
			for(i = 0; i < NAMEORDERCOUNT; i++) {
				switch(myGlobals.nameOrder[i]) {
					case 0: // custom name
					{
						// make sure we aren't in CNF_DISPLAYNC mode
						// don't get custom name for NULL contact
						if(ci->hContact != NULL && (ci->dwFlag & 0x7F) == CNF_DISPLAY) {
							BYTE dwFlag = ci->dwFlag;
							ci->dwFlag = (ci->dwFlag & CNF_UNICODE) | CNF_CUSTOMNICK;
							if(!GetContactInfo(NULL, (LPARAM)ci)) {
								ci->dwFlag = dwFlag;
								return 0;
							}
							ci->dwFlag = dwFlag;
						}
						break;
					}
					case 1: // nick
						if(!DBGetContactInfoSettingString(ci, SET_CONTACT_NICK))
							return 0;
						break;
					case 2: // First Name
						if(!DBGetContactInfoSettingString(ci, SET_CONTACT_FIRSTNAME))
							return 0;
						break;
					case 3: // E-mail
						if(!DBGetContactInfoSettingString(ci, SET_CONTACT_EMAIL))
							return 0;
						break;
					case 4: // Last Name
						if(!DBGetContactInfoSettingString(ci, SET_CONTACT_LASTNAME))
							return 0;
						break;
					case 5: // Unique id
					{
						// protocol must define a PFLAG_UNIQUEIDSETTING
						LPCSTR uidSetting = (LPCSTR)CallProtoService(ci->szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
						
						if((INT)uidSetting != CALLSERVICE_NOTFOUND && uidSetting != NULL) {	
							dbv.type = (ci->dwFlag & CNF_UNICODE) ? DBVT_WCHAR : DBVT_ASCIIZ;
							if(!DBGetSettingStr(ci->hContact, ci->szProto, uidSetting, &dbv) && !DBVariantToCIAsStr(ci, &dbv)) {
								return 0;
							}
							DBFreeVariant(&dbv);
						}
						break;
					}
					case 6: // first + last name
						if(!GCIFirstLast(ci))
							return 0;
						break;
					default: // unknown contact
						if(ci->dwFlag & CNF_UNICODE)
							ci->pszVal = (LPTSTR)mir_wcsdup(TranslateW( L"'(Unknown Contact)'"));
						else
							ci->pszVal = (LPTSTR)mir_strdup(Translate("'(Unknown Contact)'"));
						ci->type = CNFT_ASCIIZ;
						return 0;
				}
			}
		}
	}
	return 1;
}

/**
 * name:	ContactInfo_OnSettingChanged
 * desc:	if the user changes the name order update the global value
 * params:	wParam	- handle of the contact a setting was written for (must be NULL in this case)
 *			lParam	- DBCONTACTWRITESETTING structure holding information about the written data
 * return:	0
 **/
INT ContactInfo_OnSettingChanged(WPARAM wParam, LPARAM lParam)
{
	if(wParam == NULL) {
		DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING*)lParam;
		
		if(
			!mir_stricmp(cws->szModule, "Contact") &&
			!mir_stricmp(cws->szSetting, "NameOrder")
		  )
		{
			CopyMemory(myGlobals.nameOrder, cws->value.pbVal, cws->value.cpbVal);
		}
	}
	return 0;
}

/**
 * name:	ContactInfo_LoadModule
 * desc:	Loads the module at startup and replaces the service
 * params:	none
 * return:	nothing
 **/
VOID ContactInfo_LoadModule()
{
	DBVARIANT dbv;
	BYTE i;

	//if(DBGetByte(SET_GETCONTACTINFO_ENABLED, DEFVAL_GETCONTACTINFO_ENABLED)) 
	{
		// init default nameorder
		for(i = 0; i < NAMEORDERCOUNT; i++)
			myGlobals.nameOrder[i] = i;
		// load custom nameorder
		if(!DBGetSetting(NULL, "Contact", "NameOrder", &dbv)) {
			CopyMemory(myGlobals.nameOrder, dbv.pbVal, dbv.cpbVal);
			DBFreeVariant(&dbv);
		}
		// replace miranda's default getcontactinfo service routine
		DestroyServiceFunction((HANDLE)hashSetting(MS_CONTACT_GETCONTACTINFO));
		CreateServiceFunction(MS_CONTACT_GETCONTACTINFO, GetContactInfo);
	}
}